package ghttp

import (
	"context"
	"io"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"gitee.com/fkil555/gin-extend/helpers/urlhelper"
)

// 默认超时时间
const DEFAULT_TIMEOUT = 3000
const CTX_TRACEDATA_KEY = "tracedata"
const CAT_POST_LEN_LIMIT = 200

// 请求配置
type HttpConfig struct {
	Timeout int `toml:"timeout"` // 连接超时时间
	Header  map[string]string
	Cookie  []*http.Cookie
}

// 默认http配置
var defaultHttpConfig = &HttpConfig{
	Timeout: DEFAULT_TIMEOUT,
}

// 请求链路元数据
type TraceData struct {
	Host       string // 正在发起HTTP调用客户端的DOMAIN
	RequestURI string // 正在发起HTTP调用的客户端URL
	TraceStr  string // X-Trace-Str
	TraceID   string // X-Request-Id
}

// 初始化http配置
func InitHttpClient(config *HttpConfig) (err error) {
	if config != nil && config.Timeout > 0 {
		defaultHttpConfig.Timeout = config.Timeout
	}
	return
}

// 设置请求配置默认值
func setDefaultHttpConfig(userConfig *HttpConfig) (mergedConfig *HttpConfig) {
	// 用户没传, 采用默认配置
	if userConfig == nil {
		mergedConfig = defaultHttpConfig
		return
	}

	// 复制用户参数, 进行默认值覆盖
	mergedConfig = &HttpConfig{}
	*mergedConfig = *userConfig
	if mergedConfig.Timeout <= 0 {
		mergedConfig.Timeout = defaultHttpConfig.Timeout
	}
	return
}

// 通用请求（参考标准库Do方法)，但是支持CAT以及部分通用的请求配置项
func Do(ctx context.Context, rawRequest *http.Request, config *HttpConfig) (rawResponse *http.Response, err error) {
	var req *Request
	if req, err = NewDoRequest(rawRequest, config); err != nil {
		return
	}

	var resp *Response
	if resp, err = doRequest(ctx, req); err != nil {
		return
	}
	rawResponse = resp.RawResp
	return
}

// GET请求
func Get(ctx context.Context, reqUrl string, query *url.Values, config *HttpConfig) (response *Response, err error) {
	var req *Request
	if req, err = NewGetRequest(reqUrl, query, config); err != nil {
		return
	}
	return doRequest(ctx, req)
}

// POST表单
func Post(ctx context.Context, reqUrl string, query *url.Values, form *url.Values, config *HttpConfig) (response *Response, err error) {
	var req *Request
	if req, err = NewPostRequest(reqUrl, query, form, config); err != nil {
		return
	}
	return doRequest(ctx, req)
}

// POST JSON
func PostJson(ctx context.Context, reqUrl string, query *url.Values, json []byte, config *HttpConfig) (response *Response, err error) {
	var req *Request
	if req, err = NewPostJsonRequest(reqUrl, query, json, config); err != nil {
		return
	}
	return doRequest(ctx, req)
}

// POST FILE
func PostFile(ctx context.Context, reqUrl string, query *url.Values, form *url.Values, files map[string]io.Reader, config *HttpConfig) (response *Response, err error) {
	var req *Request
	if req, err = NewPostFileRequest(reqUrl, query, form, files, config); err != nil {
		return
	}
	return doRequest(ctx, req)
}

// 并发调用
func MultiRequest(ctx context.Context, reqList []*Request) (respList []*Response, err error) {
	wg := sync.WaitGroup{}
	respList = make([]*Response, len(reqList))

	for i := range respList {
		respList[i] = &Response{}
		// 关闭追踪
		reqList[i].isMulti = true
		// 协程并发
		wg.Add(1)
		go func(c context.Context, wg *sync.WaitGroup, idx int) {
			respList[idx], _ = doRequest(c, reqList[idx])
			wg.Done()
		}(ctx, &wg, i)
	}
	wg.Wait()
	return
}

// 封装网络请求(该函数返回值resp永不为空)
func doRequest(context context.Context, req *Request) (resp *Response, err error) {
	resp = &Response{}
	defer func() {
		resp.Err = err
	}()

	config := setDefaultHttpConfig(req.Config)

	client := &http.Client{
		Timeout: time.Duration(config.Timeout) * time.Millisecond, //设置默认超时时间
	}

	rawReq := req.RawReq.WithContext(context)

	// 增加自定义header
	if config.Header != nil {
		for key, value := range config.Header {
			rawReq.Header.Set(key, value)
		}
	}

	// 增加自定义cookie
	if config.Cookie != nil {
		for _, cookie := range config.Cookie {
			rawReq.AddCookie(cookie)
		}
	}

	// TODO: cron情况下没有metadata，导致无法trace parent
	{ // 请求携带链路header
		if traceData, ok := context.Value(CTX_TRACEDATA_KEY).(*TraceData); ok {
			rawReq.Header.Set("X-Request-Id", traceData.TraceID)
			rawReq.Header.Set("X-Trace-Str", traceData.TraceStr)
			parts := strings.Split(traceData.RequestURI, "?")
			cleanUrl := urlhelper.ReplaceURI(parts[0])
			rawReq.Header.Set("_catCallerDomain", traceData.Host)
			rawReq.Header.Set("_catCallerMethod", cleanUrl)
			rawReq.Header.Set("_catCallFromMethod", cleanUrl)
		}
	}
	var rawResp *http.Response

	if rawResp, err = client.Do(rawReq); err != nil {
		return
	}

	// 应答内容
	var respBody []byte

	if !req.doOnly {
		defer rawResp.Body.Close()
		if respBody, err = io.ReadAll(rawResp.Body); err != nil {
			return
		}
	}
	resp.RawResp = rawResp
	resp.Body = respBody
	return
}
